package org.geogebra.desktop.gui.view.spreadsheet;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.text.JTextComponent;

import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.plugin.EventType;
import org.geogebra.desktop.main.AppD;

import com.himamis.retex.editor.share.util.AltKeys;

public class SpreadsheetKeyListenerD implements KeyListener {

	private final AppD app;
	private final SpreadsheetViewD view;
	private final Kernel kernel;
	private final MyTableD table;
	private final DefaultTableModel model;
	private final MyCellEditorSpreadsheet editor;

	/**
	 * @param app application
	 * @param table spreadsheet table
	 */
	public SpreadsheetKeyListenerD(AppD app, MyTableD table) {
		this.app = app;
		this.kernel = app.getKernel();
		this.table = table;
		this.view = table.getView();
		this.model = (DefaultTableModel) table.getModel();
		this.editor = table.editor;
	}

	@Override
	public void keyTyped(KeyEvent e) {
		// only handle key pressed
	}

	@Override
	public void keyPressed(KeyEvent e) {
		int keyCode = e.getKeyCode();
		boolean altDown = e.isAltDown();
		boolean ctrlDown = AppD.isControlDown(e) // Windows ctrl/Mac Meta
				|| e.isControlDown(); // Fudge (Mac ctrl key)

		int row = table.getSelectedRow();
		int column = table.getSelectedColumn();

		switch (keyCode) {

		case KeyEvent.VK_UP:
			if (AppD.isControlDown(e)) {

				if (model.getValueAt(row, column) != null) {
					// move to top of current "block"
					// if shift pressed, select cells too
					while (row > 0 && model.getValueAt(row - 1, column) != null) {
						row--;
					}
					table.changeSelection(row, column, false, e.isShiftDown());
				} else {
					// move up to next defined cell
					while (row > 0 && model.getValueAt(row - 1, column) == null) {
						row--;
					}
					table.changeSelection(Math.max(0, row - 1), column, false,
							false);

				}
				e.consume();
			}
			// copy description into input bar when a cell is entered
			// GeoElement geo = (GeoElement)
			// getModel().getValueAt(table.getSelectedRow() - 1,
			// table.getSelectedColumn());
			// if (geo != null) {
			// AlgebraInput ai =
			// (AlgebraInput)(app.getGuiManager().getAlgebraInput());
			// ai.setString(geo);
			// }

			break;

		case KeyEvent.VK_LEFT:
			if (AppD.isControlDown(e)) {

				if (model.getValueAt(row, column) != null) {
					// move to left of current "block"
					// if shift pressed, select cells too
					while (column > 0
							&& model.getValueAt(row, column - 1) != null) {
						column--;
					}
					table.changeSelection(row, column, false, e.isShiftDown());
				} else {
					// move left to next defined cell
					while (column > 0
							&& model.getValueAt(row, column - 1) == null) {
						column--;
					}
					table.changeSelection(row, Math.max(0, column - 1), false,
							false);
				}

				e.consume();
			}
			// // copy description into input bar when a cell is entered
			// geo = (GeoElement) getModel().getValueAt(table.getSelectedRow(),
			// table.getSelectedColumn() - 1);
			// if (geo != null) {
			// AlgebraInput ai =
			// (AlgebraInput)(app.getGuiManager().getAlgebraInput());
			// ai.setString(geo);
			// }
			break;

		case KeyEvent.VK_DOWN:
			// auto increase spreadsheet size when you go off the bottom
			if (table.getSelectedRow() + 1 == table.getRowCount()
					&& table.getSelectedRow() + 1 < app
							.getMaxSpreadsheetRowsVisible()) {
				model.setRowCount(table.getRowCount() + 1);

				// getView().getRowHeader().revalidate(); //G.STURR 2010-1-9
			}

			else if (AppD.isControlDown(e)) {

				if (model.getValueAt(row, column) != null) {

					// move to bottom of current "block"
					// if shift pressed, select cells too
					while (row < table.getRowCount() - 1
							&& model.getValueAt(row + 1, column) != null) {
						row++;
					}
					table.changeSelection(row, column, false, e.isShiftDown());
				} else {
					// move down to next selected cell
					while (row < table.getRowCount() - 1
							&& model.getValueAt(row + 1, column) == null) {
						row++;
					}
					table.changeSelection(
							Math.min(table.getRowCount() - 1, row + 1), column,
							false, false);

				}

				e.consume();
			}

			// // copy description into input bar when a cell is entered
			// geo = (GeoElement)
			// getModel().getValueAt(table.getSelectedRow()+1,
			// table.getSelectedColumn());
			// if (geo != null) {
			// AlgebraInput ai =
			// (AlgebraInput)(app.getGuiManager().getAlgebraInput());
			// ai.setString(geo);
			// }

			break;

		case KeyEvent.VK_HOME:

			// if shift pressed, select cells too
			if (AppD.isControlDown(e)) {
				// move to top left of spreadsheet
				table.changeSelection(0, 0, false, e.isShiftDown());
			} else {
				// move to left of current row
				table.changeSelection(row, 0, false, e.isShiftDown());
			}

			e.consume();
			break;

		case KeyEvent.VK_END:

			// move to bottom right of spreadsheet
			// if shift pressed, select cells too

			// find rectangle that will contain all cells
			for (int c = 0; c < table.getColumnCount(); c++) {
				for (int r = 0; r < table.getRowCount(); r++) {
					if ((r > row || c > column)
							&& model.getValueAt(r, c) != null) {
						if (r > row) {
							row = r;
						}
						if (c > column) {
							column = c;
						}
					}
				}
			}
			table.changeSelection(row, column, false, e.isShiftDown());

			e.consume();
			break;

		case KeyEvent.VK_RIGHT:
			// auto increase spreadsheet size when you go off the right

			if (table.getSelectedColumn() + 1 == table.getColumnCount()
					&& table.getSelectedColumn() + 1 < app
							.getMaxSpreadsheetColumnsVisible()) {
				model.setColumnCount(table.getColumnCount() + 1);
				view.getColumnHeader().revalidate();

				// these two lines are a workaround for Java 6
				// (Java bug?)
				table.changeSelection(row, column + 1, false, false);
				e.consume();
			} else if (AppD.isControlDown(e)) {

				if (model.getValueAt(row, column) != null) {
					// move to bottom of current "block"
					// if shift pressed, select cells too
					while (column < table.getColumnCount() - 1
							&& model.getValueAt(row, column + 1) != null) {
						column++;
					}
					table.changeSelection(row, column, false, e.isShiftDown());
				} else {
					// move right to next defined cell
					while (column < table.getColumnCount() - 1
							&& model.getValueAt(row, column + 1) == null) {
						column++;
					}
					table.changeSelection(row,
							Math.min(table.getColumnCount() - 1, column + 1),
							false, false);

				}
				e.consume();
			}

			// // copy description into input bar when a cell is entered
			// geo = (GeoElement) getModel().getValueAt(table.getSelectedRow(),
			// table.getSelectedColumn() + 1);
			// if (geo != null) {
			// AlgebraInput ai =
			// (AlgebraInput)(app.getGuiManager().getAlgebraInput());
			// ai.setString(geo);
			// }
			break;

		case KeyEvent.VK_SHIFT:
		case KeyEvent.VK_CONTROL:
		case KeyEvent.VK_ALT:
		case KeyEvent.VK_META: // MAC_OS Meta
			e.consume(); // stops editing start
			break;

		case KeyEvent.VK_F9:
			kernel.updateConstruction(true);
			e.consume(); // stops editing start
			break;

		case KeyEvent.VK_R:
			if (AppD.isControlDown(e)) {
				kernel.updateConstruction(true);
				e.consume();
			} else {
				letterOrDigitTyped();
			}
			break;

		// needs to be here to stop keypress starting a cell edit after the undo
		case KeyEvent.VK_Z: // undo
			if (ctrlDown) {
				app.getGuiManager().undo();
				e.consume();
			} else {
				letterOrDigitTyped();
			}
			break;

		// needs to be here to stop keypress starting a cell edit after the redo
		case KeyEvent.VK_Y: // redo
			if (ctrlDown) {
				app.getGuiManager().redo();
				e.consume();
			} else {
				letterOrDigitTyped();
			}
			break;

		case KeyEvent.VK_C:
		case KeyEvent.VK_V:
		case KeyEvent.VK_X:
		case KeyEvent.VK_DELETE:
		case KeyEvent.VK_BACK_SPACE:
			if (!editor.isEditing()) {
				if (Character.isLetterOrDigit(e.getKeyChar())
						&& !editor.isEditing()
						&& !(ctrlDown || e.isAltDown())) {
					letterOrDigitTyped();
				} else if (ctrlDown) {
					e.consume();

					if (keyCode == KeyEvent.VK_C) {
						table.copy(altDown);
					} else if (keyCode == KeyEvent.VK_V) {
						boolean storeUndo = table.paste();
						view.getRowHeader().revalidate();
						if (storeUndo) {
							app.storeUndoInfo();
						}
					} else if (keyCode == KeyEvent.VK_X) {
						boolean storeUndo = table.cut();
						if (storeUndo) {
							app.storeUndoInfo();
						}
					}
				}
				if (keyCode == KeyEvent.VK_DELETE
						|| keyCode == KeyEvent.VK_BACK_SPACE) {
					e.consume();
					boolean storeUndo = table.delete();
					if (storeUndo) {
						app.storeUndoInfo();
					}
				}
				return;
			}
			break;

		// case KeyEvent.VK_ENTER:
		case KeyEvent.VK_F2:
			if (!editor.isEditing()) {
				table.setAllowEditing(true);
				table.editCellAt(table.getSelectedRow(),
						table.getSelectedColumn());
				final JTextComponent f = (JTextComponent) table
						.getEditorComponent();
				f.requestFocus();
				f.getCaret().setVisible(true);
				table.setAllowEditing(false);
			}
			e.consume();
			break;

		case KeyEvent.VK_ENTER:
			if (editor.tabReturnCol > -1) {
				table.changeSelection(row, editor.tabReturnCol, false, false);
				editor.tabReturnCol = -1;
			}

			//$FALL-THROUGH$
		case KeyEvent.VK_PAGE_DOWN:
		case KeyEvent.VK_PAGE_UP:
			// stop cell being erased before moving
			break;

		// stop TAB erasing cell before moving
		case KeyEvent.VK_TAB:
			// disable shift-tab in column A
			if (table.getSelectedColumn() == 0 && e.isShiftDown()) {
				e.consume();
			}
			break;

		case KeyEvent.VK_A:
			if (AppD.isControlDown(e)) {
				// select all cells

				row = 0;
				column = 0;
				// find rectangle that will contain all defined cells
				for (int c = 0; c < table.getColumnCount(); c++) {
					for (int r = 0; r < table.getRowCount(); r++) {
						if ((r > row || c > column)
								&& model.getValueAt(r, c) != null) {
							if (r > row) {
								row = r;
							}
							if (c > column) {
								column = c;
							}
						}
					}
				}
				table.changeSelection(0, 0, false, false);
				table.changeSelection(row, column, false, true);

				e.consume();

			}
			//$FALL-THROUGH$
		default:
			if (!Character.isIdentifierIgnorable(e.getKeyChar())
					&& !editor.isEditing() && isValidKeyCombination(e)) {
				letterOrDigitTyped();
			} else {
				e.consume();
			}
			break;

		}
	}

	private boolean isValidKeyCombination(KeyEvent e) {
		return !e.isControlDown() && (!e.isAltDown() || isSpecialCharacter(e)) && !e.isMetaDown();
	}

	private boolean isSpecialCharacter(KeyEvent e) {
		return AltKeys.isGeoGebraShortcut(e.getKeyCode(), e.isShiftDown(), false);
	}

	private void letterOrDigitTyped() {
		table.setAllowEditing(true);
		table.repaint(); // G.Sturr 2009-10-10: cleanup when keypress edit
							// begins

		// check if cell fixed
		Object o = model.getValueAt(table.getSelectedRow(),
				table.getSelectedColumn());
		if (o instanceof GeoElement) {
			GeoElement geo = (GeoElement) o;
			if (geo.isProtected(EventType.UPDATE)) {
				return;
			}
		}

		model.setValueAt(null, table.getSelectedRow(),
				table.getSelectedColumn());
		table.editCellAt(table.getSelectedRow(), table.getSelectedColumn());
		// workaround, see
		// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4192625
		final JTextComponent f = (JTextComponent) table.getEditorComponent();
		f.requestFocus();
		f.getCaret().setVisible(true);

		// workaround for Mac OS X 10.5 problem (first character typed deleted)
		if (AppD.MAC_OS) {
			SwingUtilities.invokeLater(() -> {
				f.setSelectionStart(1);
				f.setSelectionEnd(1);
			});
		}

		table.setAllowEditing(false);

	}

	@Override
	public void keyReleased(KeyEvent e) {
		// only handle key pressed
	}

}
